import { index2char, char2index } from "./constants.js";

/**
 * @param {string} str - only charCode between 0-255 is valid
 * @alias encode
 */
export function encode_8bit(str) {
  let result = "";
  const len = str.length;

  // the goal is to convert 3 x 8-bits to 4 x 6-bits
  for (let i = 0; i < len;) {
    let hasChar2 = false;
    let hasChar3 = false;
    // char is 8 bits
    const char1 = str.charCodeAt(i++);
    const char2 = (hasChar2 = i < len) ? str.charCodeAt(i++) : 0;
    const char3 = (hasChar3 = i < len) ? str.charCodeAt(i++) : 0;

    const index1 = char1 >>> 2; // char1 1111_1100
    const index2 = ((char1 & 0b11) << 4) | (char2 >>> 4); // char1 0000_0011 + char2 1111_0000
    const index3 = ((char2 & 0b1111) << 2) | (char3 >>> 6); // char2 0000_1111 + char3 1100_0000
    const index4 = char3 & 0b11_1111; // char3 0011_1111

    result += index2char(index1);
    result += index2char(index2);
    result += hasChar2 ? index2char(index3) : "=";
    result += hasChar3 ? index2char(index4) : "=";
  }

  return result;
}

/**
 * @param {string} str - only charCode between 0-255 is valid
 * @alias encode_8bit
 */
export const encode = encode_8bit;

/**
 * @param {string} str - valid base64 string
 * @returns {string} - 8-bits string
 */
export function decode(str) {
  let result = "";

  // remove padding = at the end
  const len = str.length;
  let validLen = len;
  for (let i = len - 1; i >= 0; i--) {
    if (str[i] == "=") {
      validLen--;
    } else {
      break;
    }
  }

  for (let i = 0; i < validLen; i++) {
    let hasChar3 = false;
    let hasChar4 = false;
    // char is 6 bits
    const char1 = char2index(str[i++]);
    const char2 = char2index(str[i++]);
    const char3 = (hasChar3 = i < validLen) ? char2index(str[i++]) : 0;
    const char4 = (hasChar4 = i < validLen) ? char2index(str[i]) : 0;

    // compose 4 x 6 bits to 3 x 8 bits
    const charCode1 = (char1 << 2) | (char2 >>> 4); // char1 11_1111 + char2 11_0000
    const charCode2 = ((char2 & 0b1111) << 4) | (char3 >>> 2); // char2 00_1111 + char3 11_1100
    const charCode3 = ((char3 & 0b11) << 6) | char4; // char3 00_0011 + char4 11_1111

    // here `fromCharCode` would only handle 0-255(8 bits), although it supports 16 bits.
    result += String.fromCharCode(charCode1);

    if (hasChar3) {
      result += String.fromCharCode(charCode2);
    }
    if (hasChar4) {
      result += String.fromCharCode(charCode3);
    }
  }

  return result;
}
